<?php
/**
 * Created by PhpStorm.
 * User: 王松
 * Time: 2017/10/21 22:07
 */

namespace App\Project\Search;

use Illuminate\Database\Eloquent\Builder;

abstract class Decorator
{
    protected $condition = [];

    /**
     * @var \Illuminate\Database\Eloquent\Builder
     */
    protected $db;

    /**
     * @var \App\Project\Search\Expression
     */
    protected $expression;

    /**
     * @var string
     */
    protected $table;

    /**
     * @var string
     */
    protected $prefix;

    /**
     * @var \Illuminate\Support\Collection
     */
    public static $joins;

    protected $extract = false;

    /**
     * Decorator constructor.
     * @param                                       $condition
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @param bool                                  $extract    是否 select装饰表的字段
     */
    public function __construct($condition, Builder $builder, $extract)
    {
        $this->setCondition($condition);
        $this->db         = $builder;
        $this->expression = (new Expression())->setTable($this->table);
        $this->extract    = $extract;
    }

    /**
     * @param $field
     * @param $operator
     * @return bool
     */
    protected function reject($field, $operator)
    {
        if (!array_key_exists($field, $this->getAccessField())) {
            return true;
        }

        $access_operator = $this->getAccessOperator()[$field];

        if ('all' === $access_operator) {
            return false;
        }

        return !in_array($operator, $access_operator);
    }

    /**
     * 注入表关联和搜索条件
     * @param array $join
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected function fillBuilder($join)
    {
        if ($this->condition) {
            if ($this->hasNotJoined($join[0])) {
                $this->db->join(...$join);
                static::$joins->push($join[0]);
            }
        }

        foreach ($this->condition as $field => $condition) {
            if ($this->reject($field, $condition['operator'])) {
                continue;
            }

            $this->db->whereRaw(...$this->condition($condition, $field));
        }

        return $this->db;
    }

    /**
     * \Illuminate\Database\Eloquent\Builder 中注入搜索条件
     * @param $condition
     * @param $key
     * @return mixed
     */
    protected function condition($condition, $key)
    {
        $field = $this->getAccessField()[$key];

        if (array_key_exists('value', $condition) && !empty($condition['value'])) {

            if (is_numeric($condition['value'])) {
                $condition['value'] = (int)$condition['value'];
            } elseif (is_array($condition['value'])) {
                foreach ($condition['value'] as &$item) {
                    if (is_numeric($item)) {
                        $item = (int)$item;
                    }
                }
            }

            $value = [$field, $condition['value']];
        } else {
            $value = [$field];
        }

        $expression = $this->expression($condition['operator'], $value);

        $select = explode('.', $expression[0]);
        array_splice($select, 2);

        $expression[0] = $this->convertToString($expression[0]);

        if ($this->canAddSelect($condition)) {
            $key = explode('_', $key);

            $this->addSelect(array_shift($key), $select);
        }

        return Raw::{$condition['operator']}(...$expression);
    }

    /**
     * @param string $operator
     * @param array  $condition
     * @return array
     */
    protected function expression($operator, array $condition)
    {
        return $this->expression->$operator(...$condition);
    }

    /**
     * @param $field
     * @return string
     */
    protected function convertToString($field)
    {
        if (2 <= substr_count($field, '.')) {

            $offset = strpos($field, '.', strpos($field, '.') + 1);
            $doc    = substr($field, 0, $offset);
            $path   = '$' . substr($field, $offset);

            return "JSON_EXTRACT({$doc}, '{$path}')";
        }

        return $field;
    }

    /**
     * @param $table
     * @return bool
     */
    protected function hasNotJoined($table)
    {
        if (is_null(static::$joins)) {
            static::$joins = collect($this->db->getQuery()->joins)->pluck('table');
        }

        return false === static::$joins->search($table);
    }

    /**
     * @param mixed $condition
     */
    public function setCondition($condition)
    {
        foreach ($condition as $key => $value) {
            if (
                ($value['operator'] !== 'ex' &&
                    $value['value'] !== '' &&
                    $value['value'] !== null)
                || $value['operator'] === 'ex'
            ) {
                $this->condition[$key] = $value;
            }
        }
    }

    /**
     * @return \Illuminate\Database\Eloquent\Builder
     */
    abstract public function getBuilder();

    /**
     * @return array
     */
    abstract public function getAccessField();

    /**
     * @return array
     */
    abstract public function getAccessOperator();

    /**
     * @param $key
     * @param $select
     */
    protected function addSelect($key, $select)
    {
        $this->db->addSelect(\DB::raw($this->convertToString(implode('.',
                $select)) . ' as \'' . $this->prefix . '-' . $key . '\''));
    }

    public function canAddSelect($condition)
    {
        return $this->extract && isset($condition['select']) && $condition['select'] == true;
    }
}